package it.unipd.dei.es1011.nucularc0d3.defaultproject;

import it.unipd.dei.es1011.nucularc0d3.defaultproject.util.DataWorker;
import it.unipd.dei.es1011.nucularc0d3.defaultproject.util.Details;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.hardware.SensorManager;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
/**
 * @version 3.14159265
 * @author darkfrank
 *
 */
public class DrawView extends SurfaceView {
	static class DrawThread extends Thread {
		private class Point{
			public Point(float f, float g) {
				x = f; y = g;
			}
			public float x;
			public float y;
		}
		/** Current height of the surface/canvas. */
		private int mHeight = 1;
		/** Current width of the surface/canvas. */
		private int mWidth = 1;

		/** Indicate whether the surface has been created & is ready to draw */
		private boolean mRun = false;

		/** Indicate if the X chart are showed or not */
		public boolean showX = true;
		/** Indicate if the Y chart are showed or not */
		public boolean showY = true;
		/** Indicate if the Z chart are showed or not */
		public boolean showZ = true;

		/**
		 * mState have two function:
		 * 1) take trace of what frame are showed (the first of two)
		 * 2) mState % 3 are the bitmap showed (01, 12, 20)
		 */
		private int mState = 0;

		/** This array contains the three path */
		private Path[] mPath;

		/** the paint used for the paths */
		private Paint mPaint;
		/** the paint used for the axes */
		private Paint aPaint;
		/** the paint used for the points */
		private Paint pPaint;
		/** the paint used for the points */
		private Paint bPaint;

		/** a lovely array of colors */
		private final int[] mColors = new int[] { 
				Color.RED, Color.GREEN, Color.BLUE, 
				Color.LTGRAY, Color.BLACK, Color.rgb(255, 160, 0) };

		/** handler to the bitmaps and their canvas */
		private Bitmap[] bufferBitmap;
		private Canvas[] bufferCanvas;
		/** The radius of the point */
		private float pRadius = 1;
		/** scale used for y in the graph */
		private final int yScale = 4;
		/** the y coordinates of progress bar */
		private float yProgress = 0;
		/** the offset used to draw the bitmaps, it goes from 0 to mWidth-1 */
		private int offset = 0;
		/** the zoom, for my easiness i decided to limit zoom 
		 * to four level, 80 points per frame, 40 points per frame
		 * 20 ppf, and 10 ppf */
		private int zoom = 80;
		/** this is used to store the y value of the number */
		private float yNumbers;
		/** the total number of frame who contains the entire graph */
		private int frames = 1;
		/** the sample rate, express in seconds */
		private float sample_rate;
		/** the max amount of samples */
		private float totalSamples;

		/** handler to the data of the session showed */
		private Details details;
		Vector<List<String>> samples;

		/** Handle to the surface manager object we interact with */
		private SurfaceHolder mSurfaceHolder;
		/** Handle to the Graphizer class (and context) */
		private Graphizer mContextHandler;

		/** the xml file to load */
		private String mFileName;
		private float step;

		/**
		 * Default constructor.
		 * @param context the handler of the class Graphizer used to pick up context and to call cancelDialog()
		 * @param surfaceHolder the handler to the SurfaceHolder, is where the graph are drawed
		 * @param fileName the xml file to load
		 * @param width the width of the screen
		 * @param height the height of the screen
		 */
		public DrawThread(Graphizer context, SurfaceHolder surfaceHolder, String fileName, int width, int height) {			
			// get handles to some important objects
			mContextHandler = context;
			mSurfaceHolder = surfaceHolder;
			// get the fileName of the XML file to load
			mFileName = fileName;
			// get dimension of the screen
			mWidth = width;
			mHeight = height;
			// initialize data structure
			details = new Details();
			samples = new Vector<List<String>>();
			// calculate once the y position of numbers
			yNumbers = mHeight / 2 + 15;
			//calculate the y of the progress bar (mHeight - StrokeWidth/2)
			yProgress = mHeight - 2; 
			// the paint used 4 the path
			mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
			mPaint.setStyle(Paint.Style.STROKE);
			mPaint.setStrokeWidth(2);
			// the paint used 4 the axes line and number
			aPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
			aPaint.setStyle(Paint.Style.STROKE);
			aPaint.setStrokeWidth(1);
			aPaint.setColor(mColors[3]);
			// the paint used 4 the point
			pPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
			pPaint.setStyle(Paint.Style.FILL_AND_STROKE);
			pPaint.setStrokeWidth(1);
			pPaint.setColor(mColors[4]);
			// the paint used 4 the progress bar
			bPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
			bPaint.setStyle(Paint.Style.STROKE);
			bPaint.setStrokeWidth(4);
			// create three giant bitmap and three canvas
			bufferBitmap = new Bitmap[3];
			bufferCanvas = new Canvas[3];
			bufferBitmap[0] = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
			bufferCanvas[0] = new Canvas(bufferBitmap[0]);
			bufferBitmap[1] = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
			bufferCanvas[1] = new Canvas(bufferBitmap[1]);
			bufferBitmap[2] = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
			bufferCanvas[2] = new Canvas(bufferBitmap[2]);
			// the paths
			mPath = new Path[ 3 ];
		}

		/** Load from disk data and make first frame */
		private void init(){
			DataWorker.loadSessionFromDisk(mFileName, details, samples, mContextHandler);
			// calculate the sample rate
			sample_rate = Float.parseFloat(details.sample_rate) / 1000; //calculate sample rate
			// calculate the max number of total sample
			int nx = 0, ny = 0, nz = 0, i = 0;
			if(details.x){
				nx = samples.get(i).size();
				i++;
			}
			if(details.y){
				nx = samples.get(i).size();
				i++;
			}
			if(details.z){
				nx = samples.get(i).size();
			}
			totalSamples = Math.max(nx, Math.max(ny, nz));
		}

		/** This function initialize the Path used to draw X, Y and Z axes, initialize 
		 *  the Bitmaps {0,1,2} and the Canvas {0,1,2} used to draw the graph.
		 *  It also draw the first two frames. */
		private void makeBitmap(){
			calculateParameters();

			// draw the first two frame axes line 
			drawAxesLine(0, 0);
			drawAxesLine(1, 1);

			// draw the first two frame path
			drawPath(0, 0);
			drawPath(1, 1);
		} 

		/** 
		 * This function make the path for the axes passed
		 * @param samples the list of samples
		 * @param position is used to determinate which part of the graph are to draw
		 * @param p in this Path were deposited the generated path 
		 */
		private void makeFollowPath(List<String> samples, int position, Path p, ArrayList<Point> pts ) {
			/** the size of the list */
			final int size = samples.size();
			/** in case of first point we restart from the last drawn in the previous frame. */
			if(zoom*position < size){
				if(position != 0){
					p.moveTo(0f, -yScale*Float.parseFloat(samples.get((zoom*position)-1)));
					pts.add(new Point(0f, -yScale*Float.parseFloat(samples.get((zoom*position)-1))));
				}else{
					p.moveTo(0f, -yScale*Float.parseFloat(samples.get(0)));
					pts.add(new Point(0f,-yScale*Float.parseFloat(samples.get(0))));
				}
				float x = 0f, y = 0f;

				for (int i=1; i < Math.min(zoom, size - position*zoom); i++) {

					x = i * step;
					if(i == zoom-1 )
						x = mWidth;

					y = -yScale*Float.parseFloat(samples.get(i+zoom*position));
					p.lineTo(x, y);		
					pts.add(new Point(x,y));
				}
			}
		}

		/** This function simpli caluclate how much frames are needed to draw all graph */
		private void calculateParameters(){
			//number of frames
			frames = (int) Math.ceil(totalSamples / zoom); 
			if(frames == 0)
				frames = 1;
			/** indicate the pixel from a point to another */
			step = (float)mWidth/(zoom-1);
		}

		/**
		 * This method is used to draw the axes line into the bitmap indicated from index and also
		 * draw the number. 
		 * @param index is used to determinate which bitmap is redraw
		 * @param position is used to determinate which part of the graph are to draw
		 */
		private void drawAxesLine(int index, int position){
			// reset canvas
			bufferCanvas[index].drawColor(Color.WHITE);

			// ---- draw horizontal axes lines ---- 
			bufferCanvas[index].drawLine(0, mHeight/2 - 2*yScale*SensorManager.GRAVITY_EARTH, 
					mWidth, mHeight/2 - 2*yScale*SensorManager.GRAVITY_EARTH, aPaint); // 2G line
			bufferCanvas[index].drawLine(0, mHeight/2 - yScale*SensorManager.GRAVITY_EARTH, 
					mWidth, mHeight/2 - yScale*SensorManager.GRAVITY_EARTH, aPaint); // 1G line
			bufferCanvas[index].drawLine(0, mHeight/2, mWidth, mHeight/2, aPaint); // zero line
			bufferCanvas[index].drawLine(0, mHeight/2 + yScale*SensorManager.GRAVITY_EARTH, 
					mWidth, mHeight/2 + yScale*SensorManager.GRAVITY_EARTH, aPaint); // -1G line
			bufferCanvas[index].drawLine(0, mHeight/2 + 2*yScale*SensorManager.GRAVITY_EARTH, 
					mWidth, mHeight/2 + 2*yScale*SensorManager.GRAVITY_EARTH, aPaint); // -2G line
			// --- end draw horizontal axes lines ---

			// ---- draw y numbers ----
			bufferCanvas[index].drawText("2G", 0f, mHeight/2 - 2*yScale*SensorManager.GRAVITY_EARTH, aPaint);
			bufferCanvas[index].drawText("1G", 0f, mHeight/2 - yScale*SensorManager.GRAVITY_EARTH, aPaint);
			bufferCanvas[index].drawText("-1G", 0f, mHeight/2 + yScale*SensorManager.GRAVITY_EARTH, aPaint);
			bufferCanvas[index].drawText("-2G", 0f, mHeight/2 + 2*yScale*SensorManager.GRAVITY_EARTH, aPaint);
			// --- end draw y numbers ----

			// ---- draw x numbers ----
			final float time_step = zoom/10 * sample_rate;
			final float startValue = position*9*time_step;

			for(int j = 0; j < 9; j++){
				bufferCanvas[index].drawText(
						String.format("%.01f", startValue + j * time_step), //value
						j*(mWidth/9), // x
						yNumbers, aPaint); // y
			}
			// --- end draw numbers ---			
		}

		/**
		 * This method is used to draw the path line into the bitmap indicated from index. If the details.[axes]
		 * is false, the path of the axes are not draw.
		 * @param index is used to determinate which bitmap is redraw
		 * @param position is used to determinate which part of the graph are to draw
		 */
		private void drawPath(int index, int position){
			drawPath(index, position, -1, -1);
		}

		/**
		 * This method is used to draw the path line into the bitmap indicated from index. If the details.[axes]
		 * is false, the path of the axes are not draw.
		 * @param index is used to determinate which bitmap is redraw
		 * @param position is used to determinate which part of the graph are to draw
		 * @param coloredGuy the point to color of orange
		 */
		private void drawPath(int index, int position, int coloredGuy, int axis){
			bufferCanvas[index].save();
			bufferCanvas[index].translate(0f, mHeight/2);
			int i = 0;
			if(details.x){
				if(showX){
					ArrayList<Point> pts = new ArrayList<Point>(zoom+17);
					Path p = new Path();
					makeFollowPath(samples.get(i), position, p, pts);
					mPath[i] = p;
					mPaint.setColor(mColors[0]);
					bufferCanvas[index].drawPath(mPath[i], mPaint);
					pPaint.setColor(mColors[0]);
					for(int s = 0; s < pts.size(); s++){
						if(coloredGuy > -1 && coloredGuy == s && axis == 0)
							pPaint.setColor(mColors[5]);
						bufferCanvas[index].drawCircle(pts.get(s).x, pts.get(s).y, pRadius, pPaint);
						if(coloredGuy > -1 && coloredGuy == s)
							pPaint.setColor(mColors[0]);
					}
				}
				i++;
			}
			if(details.y){
				if(showY){
					ArrayList<Point> pts = new ArrayList<Point>(zoom+17);
					Path p = new Path();
					makeFollowPath(samples.get(i), position, p, pts);
					mPath[i] = p;
					mPaint.setColor(mColors[1]);
					bufferCanvas[index].drawPath(mPath[i], mPaint);
					pPaint.setColor(mColors[1]);
					for(int s = 0; s < pts.size(); s++){
						if(coloredGuy > -1 && coloredGuy == s && axis == 1)
							pPaint.setColor(mColors[5]);
						bufferCanvas[index].drawCircle(pts.get(s).x, pts.get(s).y, pRadius, pPaint);
						if(coloredGuy > -1 && coloredGuy == s)
							pPaint.setColor(mColors[1]);
					}
				}
				i++;
			}
			if(details.z && showZ){
				ArrayList<Point> pts = new ArrayList<Point>(zoom+17);
				Path p = new Path();
				makeFollowPath(samples.get(i), position, p, pts);
				mPath[i] = p;
				mPaint.setColor(mColors[2]);
				bufferCanvas[index].drawPath(mPath[i], mPaint);
				pPaint.setColor(mColors[2]);
				for(int s = 0; s < pts.size(); s++){
					if(coloredGuy > -1 && coloredGuy == s & axis == 2)
						pPaint.setColor(mColors[5]);
					bufferCanvas[index].drawCircle(pts.get(s).x, pts.get(s).y, pRadius, pPaint);
					if(coloredGuy > -1 && coloredGuy == s)
						pPaint.setColor(mColors[2]);
				}
			}
			bufferCanvas[index].restore();
		}

		/** This function is used to redraw bitmap 0, 1 or 2 in base of the state (see the 
		 *  comment relative to the mState variable), when the screen is scrolled forward. */
		private void increaseState(){
			mState++;
			switch(mState % 3){
			case 0:
				drawAxesLine(1, mState+1);
				drawPath(1, mState+1);
				break;
			case 1:
				drawAxesLine(2, mState+1);
				drawPath(2, mState+1);
				break;
			case 2:
				drawAxesLine(0, mState+1);
				drawPath(0, mState+1);
				break;
			}	
		}

		/** This function is used to redraw bitmap 0, 1 or 2 in base of the state (see the 
		 *  comment relative to the mState variable), when the screen is scrolled backwards. */
		private void decreaseState(){
			mState--;
			switch(mState % 3){
			case 0:
				drawAxesLine(0, mState);
				drawPath(0, mState);
				break;
			case 1:
				drawAxesLine(1, mState);
				drawPath(1, mState);
				break;
			case 2:
				drawAxesLine(2, mState);
				drawPath(2, mState);
				break;
			}	
		}

		/**
		 * This method is used to scroll the graph forward and backwards.
		 * @param distance distance from down and up
		 * @param distanceX the direction of the scroll
		 */
		private void scroll(float distance, float distanceX) {
			if(distanceX > 0){ // finger scroll left direction, move picture in right direction
				if(mState != frames-1 && offset != mWidth-1) // if i'am at the first frame i won't move the camera
					offset += distance / 5; // else increase  the offset

				if(offset > mWidth-1 && mState < frames){ // if the offset is >= mWidth i want to bring it to zero
					offset = 0; // it show only one frame (the first) 
					increaseState();			
				}
			}
			else	{ // finger scroll right direction, move picture in left direction
				if(offset <= 0 && mState == 0) 
					offset = 0;
				else
					offset -= distance / 5; // else decrease  the offset

				if(offset < 0  && mState > 0){ // if the offset is >= mWidth i want to bring it to zero
					offset = mWidth-1; // it show only one frame (the first) 
					decreaseState();			
				}
			}
			synchronized(this){
				this.notify();
			}
		}

		/** This method implement the zoom in function */
		private void zoomIn(){
			if(zoom != 10){			
				zoom /= 2;

				pRadius++;

				mState *= 2;
				offset = 0;

				calculateParameters();

				drawAxesLine(mState%3, mState);
				drawPath(mState%3, mState);

				drawAxesLine((mState+1)%3, mState+1);
				drawPath((mState+1)%3, mState+1);

				synchronized(this){
					this.notify();
				}
			}
		}

		/** This method implement the zoom out function */
		private boolean zoomOut() {
			if(zoom != 80){
				zoom *= 2;

				pRadius--;

				mState /= 2;
				offset = 0;

				calculateParameters();

				drawAxesLine(mState%3, mState);
				drawPath(mState%3, mState);

				drawAxesLine((mState+1)%3, mState+1);
				drawPath((mState+1)%3, mState+1);

				synchronized(this){
					this.notify();
				}	
				return true;
			}
			return false;
		}

		/**
		 * Draws the bitmap, to the provided Canvas.
		 */
		private void doDraw(Canvas canvas) {
			// reset the canvas
			canvas.drawColor(Color.WHITE);
			// draw bitmap corresponding of the state (see mState comment) with the 
			// right x offsets.
			switch(mState % 3){
			case 0:
				canvas.drawBitmap(bufferBitmap[0], -offset, 0f, null);
				canvas.drawBitmap(bufferBitmap[1], (mWidth-1)-offset, 0f, null);
				break;
			case 1:
				canvas.drawBitmap(bufferBitmap[1], -offset, 0f, null);
				canvas.drawBitmap(bufferBitmap[2], (mWidth-1)-offset, 0f, null);
				break;
			case 2: 
				canvas.drawBitmap(bufferBitmap[2], -offset, 0f, null);
				canvas.drawBitmap(bufferBitmap[0], (mWidth-1)-offset, 0f, null);
				break;
			}

			/** draw progress bar */
			float p_step = mWidth / this.frames;
			bPaint.setColor(mColors[4]);
			canvas.drawLine(0, this.yProgress, mWidth, this.yProgress, bPaint);
			bPaint.setColor(mColors[5]);
			canvas.drawLine(mState*p_step, this.yProgress, (mState+1)*p_step, this.yProgress, bPaint);

			/** draw the current level of zoom */
			aPaint.setColor(mColors[4]);
			canvas.drawText("Zoom: " + zoom, 10, 20, aPaint);
			/** draw the exit X  in a circle */
			canvas.drawCircle(mWidth-15, 15, 10, aPaint);
			canvas.drawText( "X" , mWidth-18, 20, aPaint);
			aPaint.setColor(mColors[3]);

			/* debugging stuff */
//			aPaint.setColor(mColors[4]);
//			canvas.drawText(String.valueOf(offset), 50, 50, aPaint);
//			canvas.drawText(String.valueOf(mState), 100, 100, aPaint);
//			aPaint.setColor(mColors[3]);
		}

		/**
		 * At maximum level of zoom when a tap is done, we show a dialog with 
		 * more information about the point we think the user have pressed.
		 * We make a linear scan to find the point touched (with a distance < r)
		 * Maybe we could do better but we haven't not enough time to study a better solution.
		 * For example: linear scan only for the visible point, calculates by using 
		 * mState. 
		 * @param e MotionEvent
		 */
		public void handleSingleTap(MotionEvent e) {
			/** the radix of the circle inside the user can tap to hit a point */
			final float r = 20f;

			/** User want to exit? start ListSesssionsActivity and finish me! */
			if(Math.sqrt(Math.pow(e.getRawX() - (mWidth-15), 2) + Math.pow(e.getRawY() - 15, 2)) < r){
				Intent intent = new Intent(this.mContextHandler, ListSessionsActivity.class);
				this.mContextHandler.startActivity(intent);
				this.mContextHandler.finish();
			}

			/** if we are at the maximum level of zoom we handle the tap and show details. */
			if(zoom == 10){
				/** the x touched from user shifted in the right position of graph */
				final float x = offset + e.getRawX() + mWidth*mState; // this should go from 0 to 2*(mWidth - 1)
				/** the y touched from user shifted in the right position of graph */
				final float y = -(e.getRawY() - mHeight/2);	// this should go from - (mHeight/2) to (mHeight/2)  	
				
				/** the number of the point touched by the user */
				int pointOfImpact = -1; 
				/** the value to show on the dialog */
				String val = "";
				/** the axis to show on the dialog */
				String axis = "";

				int i = 0;
				int a = -1;
				if(details.x){
					if(showX){
						Iterator<String> it = this.samples.get(i).iterator();
						int j = 0;
						while(it.hasNext()){
							String s = it.next();

							float xp = (j*step);
							float yp = yScale*Float.parseFloat(s);

							if(Math.sqrt(Math.pow(x-xp, 2) + Math.pow(y-yp, 2)) < r){
								pointOfImpact = j;
								val = s;
								axis = "Axis: X\n";
								a = 0;
							}
							j++;
						}
					}
					i++;
				}	
				if(details.y){
					if(showY){
						Iterator<String> it = this.samples.get(i).iterator();
						int j = 0;
						while(it.hasNext()){
							String s = it.next();

							float xp = (j*step);
							float yp = yScale*Float.parseFloat(s);

							if(Math.sqrt(Math.pow(x-xp, 2) + Math.pow(y-yp, 2)) < r){
								pointOfImpact = j;
								val = s;
								axis = "Axis: Y\n";
								a = 1;
							}
							j++;
						}
					}
					i++;
				}	
				if(details.z){
					if(showZ){
						Iterator<String> it = this.samples.get(i).iterator();
						int j = 0;
						while(it.hasNext()){
							String s = it.next();

							float xp = (j*step);
							float yp = yScale*Float.parseFloat(s);

							if(Math.sqrt(Math.pow(x-xp, 2) + Math.pow(y-yp, 2)) < r){
								pointOfImpact = j;
								val = s;
								axis = "Axis: Z\n";
								a = 2;
							}
							j++;
						}
					}
				}	

				/** if there is a point of impact */
				if(pointOfImpact > -1 ){	 //&& pointOfImpact < totalSamples
					/** wanna redraw the graph with the selected point draw in orange color */
					if(pointOfImpact-zoom*mState < 10){
						// --- color new guy ---
						drawAxesLine(mState%3, mState);
						drawPath(mState%3, mState, pointOfImpact, a);
						// --- clear any previous colored guy ---
						drawAxesLine((mState+1)%3, mState+1);
						drawPath((mState+1)%3, mState+1);
					}
					else{
						// --- clear any previous colored guy ---
						drawAxesLine(mState%3, mState);
						drawPath(mState%3, mState);
						// --- color new guy ---
						drawAxesLine((mState+1)%3, mState+1);
						drawPath((mState+1)%3, mState+1, pointOfImpact-9, a);
					}

					synchronized(this){
						this.notify();
					}
					// ---- show Dialog with more information about the point ----------
					if(val != ""){
						new AlertDialog.Builder(this.mContextHandler)
						.setTitle("Info Point")
						.setMessage("Sample n. " + pointOfImpact + "\n" + 
								axis +
								"Values: " + val + " m/s \n" +
								"Date: " + details.date + " : " + details.time + "\n" +
								"Elapsed time: " + (sample_rate * pointOfImpact) + " s" )
								.setNeutralButton("Ok",
										new DialogInterface.OnClickListener() {
									public void onClick(DialogInterface dialog, int which) {	}
								})
								.show();
					}
				}
			}
		}

		/**
		 * switch X, Y or Z axis from visible to invisible and vice versa.
		 * @param id 0 = X axis, 1 = Y axis, 2 = Z axis
		 */
		public void toggle(int id) {
			switch(id){
			case 0:
				showX = !showX;
				break;
			case 1:
				showY = !showY;
				break;
			case 2:
				showZ = !showZ;
				break;
			}

			drawAxesLine(mState%3, mState);
			drawPath(mState%3, mState);

			drawAxesLine((mState+1)%3, mState+1);
			drawPath((mState+1)%3, mState+1);

			synchronized(this){
				this.notify();
			}
		}

		/**
		 * Used to signal the thread whether it should be running or not.
		 * Passing true allows the thread to run; passing false will shut it
		 * down if it's already running. Calling start() after this was most
		 * recently called with false will result in an immediate shutdown.
		 * 
		 * @param b true to run, false to shut down
		 */
		public void setRunning(boolean b) {
			mRun = b;
		}

		@Override
		public void run() {
			init();
			makeBitmap();
			mContextHandler.cancelDialog();
			while (mRun) {
				Canvas c = null;
				try {
					c = mSurfaceHolder.lockCanvas(null);
					synchronized (mSurfaceHolder) {
						if (c != null) {
							doDraw(c);
						}
					}
				} finally {
					// do this in a finally so that if an exception is thrown
					// during the above, we don't leave the Surface in an
					// inconsistent state
					if (c != null) {
						mSurfaceHolder.unlockCanvasAndPost(c);
					}
					// safe point to delete object
					System.gc();
				}
				if(c != null){
					synchronized(this){
						try {
							this.wait();
						} catch (InterruptedException e) {
							Log.e("sync", e.getMessage());
						}
					}
				}
			}
		}
	} 
	// -------------------- end DrawThread --------------------------

	/** The thread that actually draws the animation */
	private DrawThread thread;
	/** The Gesture Detector Helper */
	private GestureDetector mGestureDetector;
	/** The Touch Listener  */
	private OnTouchListener mGestureListener;

	/**
	 * The costructor of the activity
	 * @param context handler to the Graphizer instance
	 * @param fileName the xml file to load
	 * @param width the width of the screen
	 * @param height the height of the screen
	 */
	public DrawView(Graphizer context, String fileName, int width, int height) {
		super(context);

		// register our interest in hearing about changes to our surface
		SurfaceHolder holder = getHolder();

		thread = new DrawThread(context, holder, fileName, width, height);
		thread.setRunning(true);

		mGestureDetector = new GestureDetector(new SimpleOnGestureListener() {
			@Override
			public boolean onDoubleTap(MotionEvent e) {
				thread.zoomIn();
				return true;
			}

			@Override
			public boolean onSingleTapConfirmed(MotionEvent e) {
				thread.handleSingleTap(e);
				return true;
			}

			@Override
			public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
				// i'm only scrolling along the X axis                
				thread.scroll(Math.abs((e2.getX() - e1.getX())), distanceX);
				return true;
			}

			@Override
			/**
			 * Don't know why but we need to intercept this guy and return true so that the other gestures are handled.
			 * https://code.google.com/p/android/issues/detail?id=8233
			 */
			public boolean onDown(MotionEvent e) {
				//Log.d("onDown", "GestureDetector --> onDown");
				return true;
			}
		});

		mGestureListener = new View.OnTouchListener() {
			public boolean onTouch(View v, MotionEvent event) {

				if (mGestureDetector.onTouchEvent(event)) {
					return true;
				}

				return false;
			}
		};

		// attach the OnTouchListener to the image view
		setOnTouchListener(mGestureListener);

		setFocusable(true); // make sure we get key events
		setFocusableInTouchMode(true);

		thread.start();
	}

	@Override 
	public boolean onTouchEvent(MotionEvent me)
	{
		super.onTouchEvent(me);
		return mGestureDetector.onTouchEvent(me);
	}

	public void toggleX(){
		thread.toggle(0);
	}

	public void toggleY(){
		thread.toggle(1);
	}

	public void toggleZ(){
		thread.toggle(2);
	}

	/**
	 * Standard override to get key-press events.
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent msg) {
		if(keyCode == KeyEvent.KEYCODE_BACK){
			if(thread.zoomOut()){ 
				return true;
			}
			else {
				return super.onKeyDown(keyCode, msg);
			}
		}
		return super.onKeyDown(keyCode, msg);
	}

	public boolean isX(){
		return thread.details.x;
	}
	public boolean isY(){
		return thread.details.y;
	}
	public boolean isZ(){
		return thread.details.z;
	}

	protected void onResume(){
		synchronized(this){
			try {
				this.wait(50);
			} catch (InterruptedException e) {
				Log.w("Err", e.getMessage());
			}
		}
		synchronized(thread){
			thread.notify();
		}
	}
}
